package com.sunjob.mrosystem.aop;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.sunjob.common.domain.system.SysExecLog;
import com.sunjob.common.mroAnno.ControllerLog;
import com.sunjob.common.mroAnno.LogModuleType;
import com.sunjob.common.util.MROUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.common.protocol.Message;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author : Wangzurong
 * @create 2022-07-30 9:47
 * 操作日志切入
 */
@Aspect
@Component
@Slf4j
public class ExecLogAop {
    @Resource
    KafkaTemplate<String,Object> kafkaTemplate;
    /**
     * 统一切入点
     */
    private ThreadLocal<LocalDateTime> threadLocal = new ThreadLocal();

    @Pointcut("@annotation(com.sunjob.common.mroAnno.ControllerLog)")
    public void execAop() {

    }

    /**
     * 控制器方法开始之前 记录相关信息
     */
    @Before(value = "execAop()")
    public void recordExecLogBefore() {
        threadLocal.set(LocalDateTime.now());
    }

    /**
     * 发生异常时 记录日志
     */
    @AfterThrowing(pointcut = "execAop()", throwing = "e")
    public void recordExecLogAfterThrowing(JoinPoint jp, Exception e) {
            SysExecLog sysExecLog = handleCommon(jp,1);
            // 获得操作结果
            String execResult = e.getMessage();
            if(sysExecLog==null){
                return;
            }
            sysExecLog.setExecResult("'"+e.getClass()+"':'"+execResult+"'");
            sendByKafka(sysExecLog);

    }

    private void sendByKafka(SysExecLog sysExecLog) {
        kafkaTemplate.send("execLog", sysExecLog);
    }

    /**
     * 成功返回响应执行
     */
    @AfterReturning(pointcut = "execAop()",returning = "ret")
    public void recordExecLogAfterReturning(JoinPoint jp,Object ret) {
        SysExecLog sysExecLog = handleCommon(jp,0);
        String r = JSON.toJSONString(ret);
        if(r!=null&&r.length()<=1000) {
            sysExecLog.setExecResult(JSON.toJSONString(ret));
        }else if (r==null){
            sysExecLog.setExecResult("返回结果为空");
        }else{
            sysExecLog.setExecResult("超过记录的最大容量，放弃记录");
        }
        sendByKafka(sysExecLog);
    }

    public Map handleParameter(Object[] args, Parameter[] parameters) {
        Map map = new HashMap<String, String>();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Annotation[] annotations = parameter.getAnnotations();
            for (Annotation annotation : annotations) {
                switch (annotation.annotationType().getSimpleName()) {
                    case "RequestParam":
                        RequestParam declaredAnnotation = parameter.getDeclaredAnnotation(RequestParam.class);
                        map.put(declaredAnnotation.value(), args[i]);
                        break;
                    case "RequestBody":
                        map.put(parameter.getName(), args[i]);
                        break;
                    case "PathVariable":
                        PathVariable pathVariable = parameter.getDeclaredAnnotation(PathVariable.class);
                        map.put(pathVariable.value(), args[i]);
                        break;
                }
            }
        }
        return map;
    }
    public SysExecLog handleCommon(JoinPoint jp,Integer execStatu){
        try {
            // 获得该执行人id
            Long execUserId = null;
            try {
                execUserId = Long.valueOf(StpUtil.getLoginId() + "");
            } catch (Exception t) {
                // 未登录
            }
            // 获得日志时间
            LocalDateTime execTime = LocalDateTime.now();
            // 获得操作状态 0 成功执行 1 发生异常
            Integer execStatus = execStatu;

            // 获得织入方法
            Method method = ((MethodSignature) jp.getSignature()).getMethod();
            Annotation[] declaredAnnotations = method.getDeclaredAnnotations();

            // 获得日志模块
            ControllerLog controllerLog = method.getDeclaredAnnotation(ControllerLog.class);
            LogModuleType moduleType = controllerLog.value();
            String log_module = moduleType.getValue();
            // 获得形参列表
            Parameter[] parameters = method.getParameters();
            // 获得实参列表
            Object[] args = jp.getArgs();
            // 解析形参和实参
            Map map = handleParameter(args, parameters);
            // 获得请求参数
            String execParam = JSON.toJSONString(map);
            // 获得耗时
            LocalDateTime localDateTime = threadLocal.get();
            Duration between = Duration.between(localDateTime, LocalDateTime.now());
            Long cost = between.toMillis();
            SysExecLog sysExecLog = new SysExecLog(MROUtils.nextId(), log_module, null, execStatus, execTime, execParam, null, cost, execUserId,null,null);
            // 获得织入对象
            Integer execType = -1;
            // 解析方法注解 获得rl地址
            execType = handleAnnotation(declaredAnnotations,sysExecLog,method);
            sysExecLog.setExecType(execType);
            return sysExecLog;

        }catch (Exception exc){
            // 处理日志服务异常
        }
        return null;
    }

    public Integer handleAnnotation(Annotation[] declaredAnnotations,SysExecLog sysExecLog,Method method) {
        for (Annotation declaredAnnotation : declaredAnnotations) {
            switch (declaredAnnotation.annotationType().getSimpleName()) {
                case "RequestMapping":
                    RequestMapping declaredAnnotation1 = method.getDeclaredAnnotation(RequestMapping.class);
                    sysExecLog.setExecUrl(declaredAnnotation1.value()[0]);
                    return 0;
                case "PostMapping":
                    PostMapping declaredAnnotation2 = method.getDeclaredAnnotation(PostMapping.class);
                    sysExecLog.setExecUrl(declaredAnnotation2.value()[0]);
                    return 1;
                case "GetMapping":
                    GetMapping declaredAnnotation3 = method.getDeclaredAnnotation(GetMapping.class);
                    sysExecLog.setExecUrl(declaredAnnotation3.value()[0]);
                    return 2;
                case "PutMapping":
                    PutMapping declaredAnnotation4 = method.getDeclaredAnnotation(PutMapping.class);
                    sysExecLog.setExecUrl(declaredAnnotation4.value()[0]);
                    return 3;
                case "DeleteMapping":
                    DeleteMapping declaredAnnotation5 = method.getDeclaredAnnotation(DeleteMapping.class);
                    sysExecLog.setExecUrl(declaredAnnotation5.value()[0]);
                    return 4;
            }
        }
        return null;
    }
}
